﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace MyWeb.ServiceBus.Common
{
    /// <summary>
    /// 负载均衡算法
    /// </summary>
    public class LoadBalance
    {
        #region 初始化
        /// <summary>
        /// 服务列表
        /// </summary>
        private List<ServiceCenter> services;
        /// <summary>
        /// 算法
        /// </summary>
        private Algorithm algorithm = new Algorithm();
        /// <summary>
        /// 随机数
        /// </summary>
        private readonly static Random random = new Random();
        private int currentIndex;
        private SpinLock _spinLock = new SpinLock();
        public LoadBalance(Algorithm algorithm = Algorithm.Random)
        {
            this.services = new List<ServiceCenter>();
            this.algorithm = algorithm;
            this.currentIndex = 0;
        }
        public LoadBalance(ServiceCenter service, Algorithm algorithm = Algorithm.Random)
        {
            this.services = new List<ServiceCenter>(){ service };
            this.algorithm = algorithm;
            this.currentIndex = 0;
        }
        public LoadBalance(List<ServiceCenter> services, Algorithm algorithm = Algorithm.Random)
        {
            this.services = services;
            this.algorithm = algorithm;
            this.currentIndex = 0;
        }
        #endregion

        #region 核心算法

        #region 随机算法
        /// <summary>
        /// 随机
        /// </summary>
        /// <param name="serviceList"></param>
        /// <returns></returns>
        private ServiceCenter? RandomAlgorithm()
        {
            int index = random.Next(this.services.Count);
            return this.services[index];
        }
        #endregion

        #region 轮询算法
        /// <summary>
        /// 轮询
        /// </summary>
        private object? PollingAlgorithm()
        {
            // 获取当前服务器
            var server = services[currentIndex];
            // 更新索引，循环回到第一个服务器
            currentIndex = (currentIndex + 1) % services.Count;
            return server;
        }
        #endregion

        #region 源地址哈希法
        /// <summary>
        /// 源地址哈希法
        /// </summary>
        /// <param name="servers">服务列表</param>
        /// <param name="sourceAddress">服务名称/服务地址</param>
        /// <remarks>服务端做缓存时使用</remarks>
        /// <returns></returns>
        private ServiceCenter? HashAlgorithm(string serviceName)
        {
            if (string.IsNullOrWhiteSpace(serviceName))
            {
                throw new ArgumentNullException(nameof(serviceName));
            }

            // 计算源地址的哈希值
            int hashCode = 0;
            using (var md5 = MD5.Create())
            {
                byte[] inputBytes = Encoding.UTF8.GetBytes(serviceName);
                byte[] hashBytes = md5.ComputeHash(inputBytes);
                hashCode = BitConverter.ToInt32(hashBytes, 0);
            }
            // 根据哈希值选择服务器
            int index = Math.Abs(hashCode) % this.services.Count;
            return this.services[index];
        }
        #endregion

        #region 加权随机法
        /// <summary>
        /// 加权随机法
        /// </summary>
        public ServiceCenter? WeightRandomAlgorithm()
        {
            // 获取总权重
            int totalWeight = this.services.Sum(s => s.Weight);
            // 生成随机数
            double randomValue = random.NextDouble() * totalWeight;
            foreach(var server in this.services)
            {
                randomValue -= server.Weight;
                if (randomValue <= 0)
                {
                    return server;
                }
            }
            return this.services.FirstOrDefault();
        }
        #endregion 加权随机法

        #region 加权轮询法
        /// <summary>
        /// 加权轮询法
        /// </summary>
        private ServiceCenter? WeightPollingAlgorithm()
        {
            // 获取总权重
            int totalWeight = this.services.Sum(s => s.Weight);

            var index = this.currentIndex;
            // 更新索引，循环回到第一个索引
            currentIndex = (currentIndex + 1) % totalWeight;

            foreach (var server in this.services)
            {
                index -= server.Weight;
                if (index < 0)
                {
                    return server;
                }
            }
            return this.services.FirstOrDefault();
        }
        #endregion 加权轮询法

        #region 最小连接数法
        /// <summary>
        /// 最小连接数
        /// </summary>
        /// <returns></returns>
        private ServiceCenter? LeastConnectionAlgorithm()
        {
            // 找到当前连接数最少的服务器
            return this.services.OrderBy(s => s.Connections).FirstOrDefault();
        }
        #endregion

        #endregion

        public void AddServer(ServiceCenter service)
        {
            this.services.Add(service);
        }
        public void AddRangeServer(IEnumerable<ServiceCenter> services)
        {
            this.services.AddRange(services);
        }
        /// <summary>
        /// 获取服务链接
        /// </summary>
        /// <param name="serviceName"></param>
        /// <returns></returns>
        public string GetServer(string serviceName = "")
        {
            if (this.services == null || this.services.Count == 0)
            {
                return string.Empty; // 如果没有服务器，则返回 null
            }

            bool locked = false;
            _spinLock.Enter(ref locked);//获取锁

            var service = this.services.FirstOrDefault();
            if (this.services.Count > 1)
            {
                switch (this.algorithm)
                {
                    case Algorithm.Random:
                        service = this.RandomAlgorithm();
                        break;
                    case Algorithm.Polling:
                        service = (ServiceCenter?)this.PollingAlgorithm();
                        break;
                    case Algorithm.Hash:
                        service = this.HashAlgorithm(serviceName);
                        break;
                    case Algorithm.WeightRandom:
                        service = this.WeightRandomAlgorithm();
                        break;
                    case Algorithm.WeightPolling: 
                        service = this.WeightPollingAlgorithm();
                        break;
                    case Algorithm.LeastConnection:
                        service = this.LeastConnectionAlgorithm();
                        break;
                    default:
                        service = this.RandomAlgorithm();
                        break;
                }
            }
            service.Connections++;

            if (locked) _spinLock.Exit();//释放锁

            return service.Url;
        }

        /// <summary>
        /// 标记服务器的连接已经断开
        /// </summary>
        /// <param name="url"></param>
        public void ConnectionClosed(string url)
        {
            var server = this.services.FirstOrDefault(s => s.Url == url);
            if (server != null)
            {
                server.Connections--;
            }
        }
    }

    public class ServiceCenter
    {
        /// <summary>
        /// 地址
        /// </summary>
        /// <remarks>【必填】</remarks>
        [Key]
        public required string Url { get; set; }
        /// <summary>
        /// 权重
        /// </summary>
        public int Weight { get; set; }
        /// <summary>
        /// 连接数
        /// </summary>
        public int Connections { get; set; } = 0;
    }

    /// <summary>
    /// 算法
    /// </summary>
    public enum Algorithm
    {
        /// <summary>
        /// 随机
        /// </summary>
        Random = 0,
        /// <summary>
        /// 轮询
        /// </summary>
        Polling = 1,
        /// <summary>
        /// 哈希
        /// </summary>
        Hash = 2,
        /// <summary>
        /// 加权随机
        /// </summary>
        WeightRandom = 3,
        /// <summary>
        /// 加权轮询
        /// </summary>
        WeightPolling = 4,
        /// <summary>
        /// 最小连接数
        /// </summary>
        LeastConnection = 5
    }
}
